package com.hzgj.bcl.soa.rpc;

import com.hzgj.bcl.soa.Address;
import com.hzgj.bcl.soa.util.Constants;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.net.Inet4Address;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.UnknownHostException;
import java.util.Objects;

public class IpAddress implements Address {
    protected Logger logger = LoggerFactory.getLogger(getClass());
    private InetAddress ip_addr;
    private int port;

    public IpAddress(String i, int p) throws UnknownHostException {
        port = p;
        ip_addr = InetAddress.getByName(i);
    }


    public IpAddress(InetAddress i, int p) {
        ip_addr = i;
        port = p;
        if (this.ip_addr == null) {
            setAddressToLocalHost();
        }
    }


    public IpAddress(int port) {
        this(port, true);
    }

    public IpAddress(int port, boolean set_default_host) {
        this.port = port;
        if (set_default_host) {
            setAddressToLocalHost();
        }
    }

    public IpAddress(InetSocketAddress sock_addr) {
        port = sock_addr.getPort();
        ip_addr = sock_addr.getAddress();
    }

    public static IpAddress of(String ip_port) throws UnknownHostException {
        int index = ip_port.lastIndexOf(':');
        InetAddress ip_addr;
        int port = 0;
        if (index == -1) {
            ip_addr = InetAddress.getByName(ip_port);
        } else {
            ip_addr = InetAddress.getByName(ip_port.substring(0, index));
            port = Integer.parseInt(ip_port.substring(index + 1));
        }
        return new IpAddress(ip_addr, port);
    }

    private void setAddressToLocalHost() {
        try {
            ip_addr = InetAddress.getLocalHost();  // get first NIC found (on multi-homed systems)
        } catch (Exception e) {
            ip_addr = null;
        }
        if (ip_addr == null) {
            try {
                ip_addr = InetAddress.getByName(null);
            } catch (UnknownHostException e) {
            }
        }
    }

    public final InetAddress getIpAddress() {
        return ip_addr;
    }

    public final int getPort() {
        return port;
    }

    /**
     * implements the java.lang.Comparable interface
     *
     * @param o - the Object to be compared
     * @return a negative integer, zero, or a positive integer as this object is less than,
     * equal to, or greater than the specified object.
     * @throws ClassCastException - if the specified object's type prevents it
     *                            from being compared to this Object.
     * @see Comparable
     */
    @Override
    public final int compareTo(Address o) {
        int h1, h2, rc; // added Nov 7 2005, makes sense with canonical addresses

        if (this == o) {
            return 0;
        }
        if (!(o instanceof IpAddress)) {
            throw new ClassCastException("comparison between different classes: the other object is " + o);
        }
        IpAddress other = (IpAddress) o;
        if (ip_addr == null) {
            if (other.ip_addr == null) {
                return port < other.port ? -1 : (port > other.port ? 1 : 0);
            } else {
                return -1;
            }
        }

        h1 = ip_addr.hashCode();
        h2 = other.ip_addr.hashCode();
        rc = h1 < h2 ? -1 : h1 > h2 ? 1 : 0;
        return rc != 0 ? rc : port < other.port ? -1 : (port > other.port ? 1 : 0);
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) {
            return true;
        }
        if (o == null || getClass() != o.getClass()) {
            return false;
        }
        IpAddress ipAddress = (IpAddress) o;
        return port == ipAddress.port &&
                Objects.equals(ip_addr, ipAddress.ip_addr);
    }

    @Override
    public int hashCode() {
        return Objects.hash(ip_addr, port);
    }

    @Override
    public String toString() {
        StringBuilder sb = new StringBuilder();

        if (ip_addr == null) {
            sb.append("<null>");
        } else {
            if (ip_addr.isMulticastAddress()) {
                sb.append(ip_addr.getHostAddress());
            } else {
                String host_name = ip_addr.getHostName();
                sb.append(host_name);
            }
        }
        sb.append(":").append(port);
        return sb.toString();
    }

    @Override
    public int size() {
        // length (1 bytes) + 4 bytes for port
        int tmp_size = Constants.BYTE_SIZE + Constants.SHORT_SIZE;
        if (ip_addr != null) {
            // 4 bytes for IPv4, 20 for IPv6 (16 + 4 for scope-id)
            tmp_size += (ip_addr instanceof Inet4Address) ? 4 : 20;
        }
        return tmp_size;
    }

    public IpAddress copy() {
        return new IpAddress(ip_addr, port);
    }
}
